#include "config.h"

#include <gtest/gtest.h>

#include <memory>
#include <string>

using namespace cfg;

// 作用：构造一个用于 JSON Pointer 测试的对象树。
// 结构：
// root: {
//   "app": { "name": "CfgWeave" },
//   "feature": { "items": ["A", "B"], "enabled": true },
//   "special": { "/home": "slash", "~tilde": "tilde" }
// }
static std::shared_ptr<const Object>
make_pointer_sample()
{
    auto items = std::make_shared<Array>();
    items->items.push_back(std::string("A"));
    items->items.push_back(std::string("B"));

    auto feature = std::make_shared<Object>();
    feature->props["items"] = std::shared_ptr<const Array>(items);
    feature->props["enabled"] = true;

    auto app = std::make_shared<Object>();
    app->props["name"] = std::string("CfgWeave");

    auto special = std::make_shared<Object>();
    special->props["/home"] = std::string("slash");
    special->props["~tilde"] = std::string("tilde");

    auto root = std::make_shared<Object>();
    root->props["app"] = std::shared_ptr<const Object>(app);
    root->props["feature"] = std::shared_ptr<const Object>(feature);
    root->props["special"] = std::shared_ptr<const Object>(special);

    return std::shared_ptr<const Object>(root);
}

TEST(JsonPointerTest, BasicLookup)
{
    View v(make_pointer_sample());

    auto name_p = v.lookup("/app/name");
    ASSERT_TRUE(name_p.has_value());
    auto name_s = name_p->get_string();
    ASSERT_TRUE(name_s.has_value());
    EXPECT_EQ(*name_s, "CfgWeave");

    auto item1 = v.lookup("/feature/items/1");
    ASSERT_TRUE(item1.has_value());
    auto s1 = item1->get_string();
    ASSERT_TRUE(s1.has_value());
    EXPECT_EQ(*s1, "B");
}

TEST(JsonPointerTest, EscapeDecoding)
{
    View v(make_pointer_sample());

    auto slash_k = v.lookup("/special/~1home"); // ~1 -> '/'
    ASSERT_TRUE(slash_k.has_value());
    auto vs = slash_k->get_string();
    ASSERT_TRUE(vs.has_value());
    EXPECT_EQ(*vs, "slash");

    auto tilde_k = v.lookup("/special/~0tilde"); // ~0 -> '~'
    ASSERT_TRUE(tilde_k.has_value());
    auto vt = tilde_k->get_string();
    ASSERT_TRUE(vt.has_value());
    EXPECT_EQ(*vt, "tilde");
}

TEST(JsonPointerTest, EmptyPointerIsRoot)
{
    View v(make_pointer_sample());
    auto r = v.lookup("");
    ASSERT_TRUE(r.has_value());
    auto obj = r->object_view();
    ASSERT_TRUE(obj.has_value());
    ASSERT_NE(*obj, nullptr);
}

TEST(JsonPointerTest, InvalidEscapeAndIndex)
{
    View v(make_pointer_sample());

    auto bad_escape = v.lookup("/special/~2oops");
    ASSERT_FALSE(bad_escape.has_value());
    EXPECT_EQ(bad_escape.error().code, Errc::InvalidPath);

    auto non_digit_index = v.lookup("/feature/items/xx");
    ASSERT_FALSE(non_digit_index.has_value());
    EXPECT_EQ(non_digit_index.error().code, Errc::InvalidPath);
}

TEST(JsonPointerTest, TypeMismatchWhenDrilling)
{
    View v(make_pointer_sample());

    // /app/name 指向字符串，继续下钻应触发类型不匹配
    auto r = v.lookup("/app/name/extra");
    ASSERT_FALSE(r.has_value());
    EXPECT_EQ(r.error().code, Errc::TypeMismatch);
}

// 作用：构造一个包含空键名与深层数组嵌套的对象树，便于 JSON Pointer 复杂用例测试。
// 结构：
// root: {
//   "": { "x": 1 },
//   "nest": [ [ ["d0", "d1"], ["e0"] ], [ ["f0"] ] ]
// }
static std::shared_ptr<const Object>
make_complex_sample()
{
    auto lvl3_a = std::make_shared<Array>();
    lvl3_a->items.push_back(std::string("d0"));
    lvl3_a->items.push_back(std::string("d1"));

    auto lvl3_b = std::make_shared<Array>();
    lvl3_b->items.push_back(std::string("e0"));

    auto lvl2_a = std::make_shared<Array>();
    lvl2_a->items.push_back(std::shared_ptr<const Array>(lvl3_a));
    lvl2_a->items.push_back(std::shared_ptr<const Array>(lvl3_b));

    auto lvl3_c = std::make_shared<Array>();
    lvl3_c->items.push_back(std::string("f0"));

    auto lvl2_b = std::make_shared<Array>();
    lvl2_b->items.push_back(std::shared_ptr<const Array>(lvl3_c));

    auto lvl1 = std::make_shared<Array>();
    lvl1->items.push_back(std::shared_ptr<const Array>(lvl2_a));
    lvl1->items.push_back(std::shared_ptr<const Array>(lvl2_b));

    auto empty_key_obj = std::make_shared<Object>();
    empty_key_obj->props["x"] = int64_t{1};

    auto root = std::make_shared<Object>();
    root->props[""] = std::shared_ptr<const Object>(empty_key_obj);
    root->props["nest"] = std::shared_ptr<const Array>(lvl1);

    return std::shared_ptr<const Object>(root);
}

TEST(JsonPointerTest, ComplexEmptyKeyAndDeepArrayRead)
{
    View v(make_complex_sample());

    // 空键名段："/" 代表进入键为 "" 的对象
    auto empty_obj = v.lookup("/");
    ASSERT_TRUE(empty_obj.has_value());
    auto x = empty_obj->lookup("/x");
    ASSERT_TRUE(x.has_value());
    auto xv = x->get_int64();
    ASSERT_TRUE(xv.has_value());
    EXPECT_EQ(*xv, 1);

    // 深层数组：/nest/0/1/0 -> "e0"
    auto deep1 = v.lookup("/nest/0/1/0");
    ASSERT_TRUE(deep1.has_value());
    auto s1 = deep1->get_string();
    ASSERT_TRUE(s1.has_value());
    EXPECT_EQ(*s1, "e0");

    // 深层数组：/nest/1/0/0 -> "f0"
    auto deep2 = v.lookup("/nest/1/0/0");
    ASSERT_TRUE(deep2.has_value());
    auto s2 = deep2->get_string();
    ASSERT_TRUE(s2.has_value());
    EXPECT_EQ(*s2, "f0");
}

TEST(JsonPointerTest, SetPointerWriteThenRead)
{
    Builder b;
    // 写入空键名对象的成员："//y" -> 根对象下键 "" 对象的键 "y"
    b.set_pointer("//y", int64_t{42});
    // 写入深层数组：创建 nest[0][1][2] = "Z"
    b.set_pointer("/nest/0/1/2", std::string("Z"));
    // 写入带转义的键名：/s~1p/~~/v -> 键 "s/p" -> 键 "~" -> 键 "v"
    b.set_pointer("/s~1p/~0~/v", std::string("ok"));

    auto root = b.freeze();
    ASSERT_TRUE(root.has_value());

    View v(*root);

    auto y = v.lookup("//y");
    ASSERT_TRUE(y.has_value());
    auto yv = y->get_int64();
    ASSERT_TRUE(yv.has_value());
    EXPECT_EQ(*yv, 42);

    auto z = v.lookup("/nest/0/1/2");
    ASSERT_TRUE(z.has_value());
    auto zv = z->get_string();
    ASSERT_TRUE(zv.has_value());
    EXPECT_EQ(*zv, "Z");

    auto ok = v.lookup("/s~1p/~0~/v");
    ASSERT_TRUE(ok.has_value());
    auto oks = ok->get_string();
    ASSERT_TRUE(oks.has_value());
    EXPECT_EQ(*oks, "ok");
}

// 验证 set_pointer 的写入语义与错误条件：
// 1) 段为非负整数时作为数组索引，若无“基槽位”，报告 InvalidPath（例如路径以 "/0" 开头）。
// 2) 段不是非负整数时作为对象键名（允许 "-1"、"xx" 等），可正常写入。
// 3) 若原有槽位为标量，继续下钻会将其提升为容器而非报错。
TEST(JsonPointerTest, SetPointerInvalidArrayIndexWriteErrors)
{
    // 场景 1：未预先创建为数组时，"-1" 被视为对象键名，写入成功
    {
        Builder b;
        b.set_pointer("/items/-1", std::string("bad"));
        auto r = b.freeze();
        ASSERT_TRUE(r.has_value());
        View v(*r);
        auto got = v.lookup("/items/-1");
        ASSERT_TRUE(got.has_value());
        auto s = got->get_string();
        ASSERT_TRUE(s.has_value());
        EXPECT_EQ(*s, "bad");
    }

    // 场景 2：未预先创建为数组时，"xx" 被视为对象键名，写入成功
    {
        Builder b;
        b.set_pointer("/items/xx", int64_t{1});
        auto r = b.freeze();
        ASSERT_TRUE(r.has_value());
        View v(*r);
        auto got = v.lookup("/items/xx");
        ASSERT_TRUE(got.has_value());
        auto x = got->get_int64();
        ASSERT_TRUE(x.has_value());
        EXPECT_EQ(*x, 1);
    }

    // 场景 3：路径以数组索引开头（无基槽位），应报告 InvalidPath
    {
        Builder b;
        b.set_pointer("/0", std::string("oops"));
        auto r = b.freeze();
        ASSERT_FALSE(r.has_value());
        ASSERT_FALSE(r.error().empty());
        EXPECT_EQ(r.error()[0].code, Errc::InvalidPath);
    }

    // 场景 4：先将 items 设为数组元素，再使用非数字段会将同名槽位提升为对象并覆盖原数组
    {
        Builder b;
        b.set_pointer("/items/0", int64_t{7}); // 先产生数组
        b.set_pointer("/items/xx", int64_t{2}); // 再作为对象键写入，会将 items 槽位转为对象
        auto r = b.freeze();
        ASSERT_TRUE(r.has_value());
        View v(*r);
        auto got = v.lookup("/items/xx");
        ASSERT_TRUE(got.has_value());
        auto x = got->get_int64();
        ASSERT_TRUE(x.has_value());
        EXPECT_EQ(*x, 2);
    }

    // 场景 5：类型提升——先写入标量，再在其下继续下钻，最终写入成功
    {
        Builder b;
        b.set_pointer("/x", std::string("scalar"));
        b.set_pointer("/x/0", std::string("ok"));
        auto r = b.freeze();
        ASSERT_TRUE(r.has_value());
        View v(*r);
        auto got = v.lookup("/x/0");
        ASSERT_TRUE(got.has_value());
        auto s = got->get_string();
        ASSERT_TRUE(s.has_value());
        EXPECT_EQ(*s, "ok");
    }
}